/**
 * Interact with Google map
 * @param {View} shell
 * @constructor
 * @augments BaseMediator
 * @see http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js
 */
function MapMediator(shell)
{
	"use strict";

	this.init(shell, '#map');

	/** @type {MapMediator} */
	var _this = this;
	/** @type {Set} */
	var _set = null;
	/** @type {google.maps.Map} */
	var _googleMap = null;
	/** @type {Number} */
	var _previousZoom = 0;
	/** @type {google.maps.MapTypeId} */
	var _previousType = null;
	/** Whether to record zoom and map-type changes so they can be restored */
	var _recordChanges = false;
	/**
	 * Whether map has been enlarged before
	 * @type {Boolean}
	 * @private
	 */
	var _firstEnlarge = true;
	/** @type {Array.<Function>} Queue of zoom functions */
	var _resizeQueue = [];
	/** @type {jQuery} */
	var $closer = $('#map-close');
	/** @type {jQuery} */
	var $enlarger = _this.view.find('a');
	/** @enum {google.maps.MarkerImage} */
	var _mark = {
		yellow: new google.maps.MarkerImage('/image/yellow-dot.png',
			new google.maps.Size(19, 32),
			new google.maps.Point(0, 0),
			new google.maps.Point(9, 32)				
		),
		red: new google.maps.MarkerImage('/image/red-dot.png',
			new google.maps.Size(19, 32),
			new google.maps.Point(0, 0),
			new google.maps.Point(9, 32)
		),
		shadow: new google.maps.MarkerImage('/image/marker-shadow.png',
			new google.maps.Size(27, 20),
			new google.maps.Point(0, 0),
			new google.maps.Point(1, 20)
		)
	};
	/** Whether map is in full-screen state */
	this.isLarge = false;

	function init()
	{
		_this.bar.append($('<button>')
			.attr('title', 'Enlarge Map')
			.addClass('enlarge')
			.appendIcon('resize-full', true)
			.append('Enlarge')
			.click(enlarge)
		);

		$closer.click(function()
		{
			_recordChanges = false;
			Session.set.previewOnMap(false);

			_this.content.hide();

			_this.view
				.css('top', '10px')
				.css('right', '10px')
				.height(208)
				.width(Enum.map.smallSize + Enum.map.smallBorder);

			_this.content
				.width(Enum.map.smallSize)
				.height(Enum.map.smallSize)
				.show();

			_googleMap.setOptions({ mapTypeControl: false });

			_this.setType(Enum.map.smallType)
				.refreshSize()
				.fitSet()
				.centerOnPhoto()
				.zoomIn(2);

			$closer.hide();
			$enlarger.show();

			_this.isLarge = false;
			_this.handleClosed();
		});
	}

	function enlarge()
	{
		_this.handleEnlarged();
		Session.set.previewOnMap();

		_this.content.hide();

		_this.view.css({
			top: Enum.map.largeBorder + 'px',
			right: Enum.map.largeBorder + 'px',
			width: (window.innerWidth - Enum.map.largeBorder * 2) + 'px',
			height: (window.innerHeight - Enum.map.largeBorder * 2) + 'px'
		});

		_this.content
			.width(_this.view.width() - Enum.map.smallBorder)
			.height(_this.view.height() - Enum.map.smallBorder)
			.show();

		_this.setType((_previousType) ? _previousType : Enum.map.largeType).refreshSize();

		if (_firstEnlarge)
		{
			_this.fitSet();
			_firstEnlarge = false;
		}
		else
		{
			_this.centerOnPhoto(_photo).zoomTo(_previousZoom);
		}
		$closer.show();
		$enlarger.hide();
		_googleMap.setOptions({ mapTypeControl: true });
		_this.isLarge = true;
		_recordChanges = true;
	}

	/**
	 * Toggle size of map
	 * @return {MapMediator}
	 */
	this.toggle = function()
	{
		if (_this.isLarge) { _this.shrink(); } else { _this.enlarge(); }
		return _this;
	};

	/**
	 * Close the map overlay
	 */
	this.close = function() { $closer.click(); };

	/**
	 * Show set of photos on the map
 	 * @return {MapMediator}
	 */
	this.showSet = function()
	{
		_previousZoom = 0;
		_firstEnlarge = true;
		
		if (_googleMap == null)
		{
			_this.content.empty();

			_googleMap = new google.maps.Map(_this.content[0],
			{
				mapTypeId: Enum.map.smallType,
				panControl: false,
				mapTypeControl: false,
				streetViewControl: false,			
				mapTypeControlOptions:
				{
					style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
					mapTypeIds: [Enum.map.smallType, Enum.map.largeType]
				}
			});
			
			google.maps.event.addListener(_googleMap, 'zoom_changed', function()
			{
				if (_recordChanges) { _previousZoom = _googleMap.getZoom(); }
			});
			
			google.maps.event.addListener(_googleMap, 'maptypeid_changed', function()
			{
				if (_recordChanges) { _previousType = _googleMap.getMapTypeId(); }
			});
			
			google.maps.event.addListener(_googleMap, 'bounds_changed', function()
			{
				var i = 0;
				var fn = _resizeQueue.shift();			
				while (fn != null && i < 10) { fn(); fn = _resizeQueue.shift(); i++; }
			});
		}
		else if (_set != null)
		{
			// remove the previous set from the map
			_set.removeFromMap();
		}
		
		_set = Session.set;

		if (_set.addToMap(_googleMap, _mark.red, _mark.shadow, _this.handleMarkerClick))
		{
			_this.enabled = true;
			_this.show();
			_resizeQueue.push(function() { _this.zoomIn(2); });
		}		
		return _this;
	};

	/**
	 * Size and position map to display set photos
	 * @return {MapMediator}
	 */
	this.fitSet = function()
	{
		_googleMap.fitBounds(_set.boundary);
		return _this;
	};

	/**
	 * Center map on a photo
	 * @return {MapMediator}
	 * @see https://developers.google.com/maps/documentation/javascript/overlays
	 */
	this.centerOnPhoto = function()
	{
		if (Session.photo.mapMarker != null) { _googleMap.setCenter(Session.photo.mapMarker.getPosition()); }
		return _this;
	};

	/**
	 * Set map type (satellite, terrain, etc.)
	 * @param {google.maps.MapTypeId} type
	 * @return {MapMediator}
	 * @see https://developers.google.com/maps/documentation/javascript/maptypes
	 */
	this.setType = function(type)
	{
		_googleMap.setMapTypeId(type);
		return _this;
	};

//- Sizing --------------------------------------------------------------------

	/**
	 * If enabled, change map to large overlay
	 * @return {MapMediator}
	 */
	this.enlarge = function()
	{
		if (_this.enabled) { $enlarger.click(); }
		return _this;
	};

	/**
	 * Change map to small overlay
	 * @return {MapMediator}
	 */
	this.shrink = function()
	{
		$closer.click();
		return _this;
	};

	/**
	 * Force refresh of the map
	 * @return {MapMediator}
	 * @see https://developers.google.com/maps/documentation/javascript/events
	 */
	this.refreshSize = function()
	{
		google.maps.event.trigger(_googleMap, 'photoResize');
		return _this;
	};

	/**
	 * Zoom google map by some amount
	 * @param {Number} add How much more to zoom
	 * @return {MapMediator}
	 */
	this.zoomIn = function(add)
	{
		var z = _googleMap.getZoom();
		if (z) { _googleMap.setZoom(z + add); }
		return _this;
	};

	/**
	 * Zoom map to a specific amount
	 * @param {Number} z Zoom level
	 * @return {MapMediator}
	 */
	this.zoomTo = function(z)
	{
		if (z) { _googleMap.setZoom(z); console.log(z); }
		return _this;
	};

//- Highlights ----------------------------------------------------------------

	/**
	 * Highlight a photo on the map with different icon and center positioning
	 * @see https://developers.google.com/maps/documentation/javascript/overlays
	 * @see http://www.mapchannels.com/Markers.aspx
	 * @return {MapMediator}
	 */
	this.highlightPhoto = function()
	{
		if (_this.enabled && _this.visible && Session.photo.mapMarker != null)
		{
			Session.photo.mapMarker.setIcon(_mark.yellow);
			_this.centerOnPhoto();
		}
		return _this;
	};

	/**
	 * Reset photo to unhighlighted state with normal icon
	 * @return {MapMediator}
	 */
	this.clearHighlights = function()
	{
		if (Session.photo != null && Session.photo.mapMarker != null)
		{
			Session.photo.mapMarker.setIcon(_mark.red);
		}
		return _this;
	};


//- Handlers ------------------------------------------------------------------

	/**
	 * Shadow to implement
	 */
	this.handleClosed = function() { };

	/**
	 * Shadow to implement
	 */
	this.handleEnlarged = function() { };

	/**
	 * Shadow to implement
	 * @param {Photo} photo Photo for clicked marker
	 */
	this.handleMarkerClick = function(photo) { };

	init();
}
MapMediator.inherit(BaseMediator);